package cn.legym.garp.gateway.traffic.dynamic.watcher;

import cn.legym.garp.gateway.traffic.dynamic.SentinelDatasourceWatcher;
import cn.legym.garp.gateway.traffic.dynamic.context.SentinelPropertyProxyFactory;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.google.common.collect.Maps;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Field;
import java.util.Map;

/**
 * create by pipisun on 2021/9/13
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * last update by {} on {}
 * update description:
 */
@Slf4j
public class NacosDatasourceWatcher extends AbstractSentinelDatasourceWatcher {

    private final Map<RuleType, NacosDataSourceWrapper> dataSourceMap = Maps.newConcurrentMap();

    @Override
    public void onInit(ApplicationContext context, SentinelProperties sentinelProperties) {
        sentinelProperties.getDatasource().forEach((name, src) -> {
            RuleType ruleType = src.getValidDataSourceProperties().getRuleType();
            String beanName = name + "-sentinel-nacos-datasource";
            NacosDataSource<?> bean = context.getBean(beanName, NacosDataSource.class);
            dataSourceMap.put(ruleType, NacosDataSourceWrapper.builder()
                    .name(beanName)
                    .source(bean)
                    .config(src)
                    .ruleType(ruleType).build());
        });
        dataSourceMap.forEach((k, v) -> v.getSource().getProperty().addListener(new Listener(v.getRuleType(), this)));
        // 动态代理 SentinelProperty
        // 主动找到并替换持有的SentinelProperty引用
        dataSourceMap.forEach((k, v) -> {
            try {
                NacosDataSource<?> source = v.getSource();
                SentinelProperty<?> proxy = SentinelPropertyProxyFactory.create(source.getProperty());
                Class<?> clazz = source.getClass().getSuperclass();
                Field field = clazz.getDeclaredField("property");
                field.setAccessible(true);
                field.set(source, proxy);
            } catch (Exception e) {
                log.error("", e);
            }
        });
        sentinelProperties.getDatasource().forEach((name, src) -> {
            RuleType ruleType = src.getValidDataSourceProperties().getRuleType();
            NacosDataSourceWrapper wrapper = dataSourceMap.get(ruleType);
            if (wrapper != null) {
                src.getValidDataSourceProperties().postRegister(wrapper.getSource());
            }
        });
    }

    @Override
    public SentinelProperty<?> getSourceByType(RuleType ruleType) {
        NacosDataSourceWrapper wrapper = dataSourceMap.get(ruleType);
        if (wrapper == null) {
            return null;
        }
        return wrapper.getSource().getProperty();
    }

    @Data
    @Builder
    private static class NacosDataSourceWrapper {
        private String name;
        private NacosDataSource<?> source;
        private RuleType ruleType;
        private DataSourcePropertiesConfiguration config;
    }

    private static class Listener implements PropertyListener {

        private final RuleType ruleType;
        private final SentinelDatasourceWatcher watcher;

        public Listener(RuleType ruleType, SentinelDatasourceWatcher watcher) {
            this.ruleType = ruleType;
            this.watcher = watcher;
        }

        @Override
        public void configUpdate(Object value) {
            this.watcher.getListeners().forEach(listener -> listener.onChange(this.ruleType, value));
        }

        @Override
        public void configLoad(Object value) {
            this.watcher.getListeners().forEach(listener -> listener.onChange(this.ruleType, value));
        }
    }
}
